#pragma once

class CCommandArg
{
	friend class CCommand;
	friend class CCommands;
	friend class COperation;

protected:
	int						m_nIndex;
	const CTypeBase		*	m_pType;
	int						m_nSize;
	eTokenType				m_eTokenType;
	const CToken		*	m_pDefaultValue;
	bool					m_bIsReturn				: 1;
	bool					m_bIsParsed				: 1;
	bool					m_bIsLabel				: 1;
	bool					m_bIsVarArgs			: 1;
	bool					m_bIsNumArgs			: 1;
	bool					m_bIsAmbiguous			: 1;
	bool					m_bIsUnknown			: 1;
	bool					m_bIsOperand			: 1;		// part of an operation?

	CCommandArg(int nIndex, const CTypeBase * pType, eArgFlag eFlags, const CToken * pDefaultVal = nullptr) : m_nIndex(nIndex),
		m_pType(pType),
		m_eTokenType(pType ? pType->GetTokenType() : TOKEN_NONE),
		m_pDefaultValue(pDefaultVal),
		// TODO: something better...
		m_bIsReturn((eFlags & ARGFLAG_RETURN) != ARGFLAG_NONE),
		m_bIsParsed((eFlags & ARGFLAG_PARSABLE) != ARGFLAG_NONE),
		m_bIsLabel(pType ? pType->GetTokenType() == TOKEN_LABEL : false),
		m_bIsVarArgs((eFlags & ARGFLAG_VAR_EXTRA_PARAMS) != ARGFLAG_NONE),
		m_bIsNumArgs((eFlags & ARGFLAG_NUM_EXTRA_PARAMS) != ARGFLAG_NONE),
		m_bIsAmbiguous((eFlags & ARGFLAG_AMBIGUOUS) != ARGFLAG_NONE),
		m_bIsUnknown((eFlags & ARGFLAG_UNKNOWN_PARAM) != ARGFLAG_NONE),
		m_bIsOperand((eFlags & (ARGFLAG_LEFT_OPERAND | ARGFLAG_OPERAND)) != ARGFLAG_NONE)
	{
	}

	inline void					SetType(const CType * pType)				{m_pType = pType;}

public:
	inline bool					IsReturn() const							{return m_bIsReturn;}
	inline bool					IsParsed() const							{return m_bIsParsed;}
	inline bool					IsLabel() const								{return m_bIsLabel;}
	inline bool					IsVarArgs() const							{return m_bIsVarArgs;}
	inline bool					IsNumArgs() const							{return m_bIsNumArgs;}
	inline bool					IsAmbiguous() const							{return m_bIsAmbiguous;}
	inline bool					IsUnknown() const							{return m_bIsUnknown;}
	inline const CTypeBase	*	GetType() const								{return m_pType;}
	inline eTokenType			GetTokenType() const						{return m_eTokenType;}
	//inline const CEnum		*	GetEnum() const								{return m_pEnum;}
	inline int					GetIndex() const							{return m_nIndex;}
	inline int					GetSize() const								{return m_nSize;}
	inline const CToken		*	GetDefault() const							{return m_pDefaultValue;}

	bool IsCompatible(const CTypeBase * pType) const
	{
		// Dont break teh rules!
		if(IsAmbiguous() || IsParsed()) return true;
		if(IsReturn())
		{
			if(!pType->IsAnyVar()) return false;
			return GetType()->IsAnyVar()	? pType->GetVarType() == GetType()->GetVarType() && pType->GetBasicType() == GetType()->GetBasicType()
											: pType->GetBasicType() == GetType()->GetBasicType();
		}
		if(GetType()->IsString() && pType->IsTextLabel())
			return pType->GetNumericType() == GetType()->GetNumericType();
		return GetType()->IsJustAnyVar()	? pType->GetVarType() == GetType()->GetVarType()
											: pType->GetBasicType() == GetType()->GetBasicType();
	}
	bool IsStrictlyCompatible(const CTypeBase * pType) const
	{
		// We got a few more rules...
		if(IsAmbiguous() || IsParsed()) return true;
		if(GetType()->IsExtended())
		{
			if(!pType->IsExtended()) return false;
			auto pExType = pType->Extend();
			return pExType->GetInternalID().m_wIdx == GetType()->Extend()->GetInternalID().m_wIdx;
		}
		if(GetType()->IsJustAnyVar()) return pType->GetVarType() == GetType()->GetVarType();
		return GetType()->IsAnyVar()	? pType->GetVarType() == GetType()->GetVarType() && pType->GetBasicType() == GetType()->GetBasicType()
										: pType->GetBasicType() == GetType()->GetBasicType();
	}
};

class CCommand : public COpcode, public CIdentifier
{
	friend class CCommands;
	
	int							m_nNumParams;
	int							m_nNumInArgs;
	int							m_nNumOutArgs;
	//int						m_nNumInParams;
	//int						m_nNumOutParams;
	bool						m_bIsScriptCommand		: 1;
	bool						m_bIsOverloaded			: 1;
	bool						m_bIsAmbiguous			: 1;
	CCommand				*	m_pNextOverload;
	CCommand				*	m_pPrevOverload;
	COperation				*	m_pOperation;				// for commands masquerading as operators
	CLListEx<CCommandArg*>		m_Args;
	//CParamType				m_InParams[MAX_PARAMS];
	//CParamType				m_OutParams[MAX_PARAMS];
	//CEnum					*	m_pParamEnums[MAX_PARAMS];

protected:
	CCommand(DWORD id, const char *szName) : COpcode(id, OPCODE_TYPE_COMMAND), CIdentifier(szName),
		m_nNumParams(0), m_nNumInArgs(0), m_nNumOutArgs(0), m_bIsScriptCommand(false),
		m_bIsOverloaded(false), m_bIsAmbiguous(false), m_pNextOverload(nullptr),
		m_pPrevOverload(nullptr), m_pOperation(nullptr)
	{
		//memset(m_pParamEnums, 0, sizeof(m_pParamEnums));
	}
	virtual ~CCommand()
	{
	}

	inline void					IsOverloaded(bool b)			{m_bIsOverloaded = b;}
	inline void					IsAmbiguous(bool b)				{m_bIsAmbiguous = b;}

	
	CCommandArg * AddInArg(const CTypeBase * pType, eArgFlag eFlags)
	{
		// But you can...
		/*if(m_nNumOutArgs)
			throw("Cannot pass an argument after the first return (%s %s)", GetName(), pType->GetName());*/
		auto pCommandArg = new CCommandArg(m_nNumParams++, pType, eFlags);
		if(pCommandArg)
		{
			if(IsAmbiguous()) pCommandArg->m_bIsAmbiguous = true;
			m_Args.Add(pCommandArg);
			++m_nNumInArgs;
		}
		return pCommandArg;
	}
	CCommandArg * AddOutArg(const CTypeBase * pType, eArgFlag eFlags)
	{
		auto pCommandArg = new CCommandArg(m_nNumParams++, pType, eFlags);
		if(pCommandArg)
		{
			if(IsAmbiguous()) pCommandArg->m_bIsAmbiguous = true;
			m_Args.Add(pCommandArg);
			++m_nNumOutArgs;
		}
		return pCommandArg;
	}

public:
	CCommand							*	AsCommand() override			{return this;}
	virtual class CConsoleCommand		*	AsConsole()						{return nullptr;}
	virtual bool							IsConsole()						{return false;}
	void									SetOperation(COperation *p)		{m_pOperation = p;}
	void									SetIsUnknown(bool b)			{m_bType = b ? OPCODE_TYPE_UNKNOWN : OPCODE_TYPE_COMMAND;}
	void									SetIsScript(bool b)				{m_bIsScriptCommand = b;}
	void									SetNextOverload(CCommand *p)	{m_pNextOverload = p; IsOverloaded(true);}
	void									SetPrevOverload(CCommand *p)	{m_pPrevOverload = p; IsOverloaded(true);}

	COperation							*	GetOperation()					{return m_pOperation;}

	const CLListEx<const CCommandArg*>	*	GetArgs() const					{return (CLListEx<const CCommandArg*>*)&m_Args;}
	int										GetNumArgs() const				{return m_nNumParams;}
	int										GetNumInArgs() const			{return m_nNumInArgs;}
	int										GetNumOutArgs() const			{return m_nNumOutArgs;}
	//CParamType							GetParam(int idx)				{return m_InParams[idx];}
	//CParamType							GetReturn(int idx)				{return m_OutParams[idx];}
	//int									GetNumParams()					{return m_nNumInParams;}
	//int									GetNumReturns()					{return m_nNumOutParams;}
	//CEnum								*	GetEnumForParam(int idx)		{return m_pParamEnums[idx];}
	CCommand							*	GetNextOverload()				{return m_pNextOverload;}
	CCommand							*	GetPrevOverload()				{return m_pPrevOverload;}
	const CCommand						*	GetNextOverload() const			{return m_pNextOverload;}
	const CCommand						*	GetPrevOverload() const			{return m_pPrevOverload;}

	bool									IsOverloaded() const			{return m_bIsOverloaded;}
	bool									IsAmbiguous() const				{return m_bIsAmbiguous;}
	
	const CCommandArg * AddArg(const CTypeBase * pType, eArgFlag eFlags)
	{
		if((eFlags & ARGFLAG_AMBIGUOUS) == ARGFLAG_NONE && !pType)
			throw(SCRambl.BuildText(SCRambl.GetText("COMMANDS", "M104"), NULL, 0, "%s", GetName()));
		if(m_nNumParams >= MAX_PARAMS)
			throw(SCRambl.BuildText(SCRambl.GetText("COMMANDS", "M105"), NULL, 0, "%s%d", GetName(), MAX_PARAMS));
		auto pArg = eFlags & ARGFLAG_RETURN ? AddOutArg(pType, eFlags) : AddInArg(pType, eFlags);
		if(!pArg) throw(SCRambl.BuildText(SCRambl.GetText("COMMANDS", "M106"), NULL, 0, "%s%s", GetName(), pType ? pType->GetName() : "ANY"));
		if(pArg->IsAmbiguous()) IsAmbiguous(true);
		return pArg;
	}

	/*void AddEnumForArg(int i, CEnum *pEnum)
	{
		auto pCommandArg = GetArg(i);
		if(pCommandArg) pCommandArg->m_pEnum = pEnum;
	}*/

	const CCommandArg * GetArg(int i) const
	{
		if(i >= 0 && i < GetNumArgs())
		{
			int n = 0;
			auto pLink = m_Args.Begin();
			while(pLink != m_Args.Bad() && ++n <= i) ++pLink;
			if(i == n-1) return *pLink;
		}
		return nullptr;
	}

	int GetNumParseArgs()
	{
		int n = 0;
		for(auto pLink = m_Args.Begin(); pLink != m_Args.Bad(); ++pLink)
		{
			if(pLink->IsParsed()) ++n;
		}
		return n;
	}
	int GetNumCompatibleArgs(const CTypeBase * pType)
	{
		int n = 0;
		for(auto pLink = m_Args.Begin(); pLink != m_Args.Bad(); ++pLink)
		{
			if(pLink->IsCompatible(pType)) ++n;
		}
		return n;
	}
	int GetNumCompatibleArgsStrict(const CTypeBase * pType)
	{
		int n = 0;
		for(auto pLink = m_Args.Begin(); pLink != m_Args.Bad(); ++pLink)
		{
			if(pLink->IsStrictlyCompatible(pType)) ++n;
		}
		return n;
	}
	int GetNumCompatibleInArgs(const CTypeBase * pType)
	{
		int n = 0;
		for(auto pLink = m_Args.Begin(); pLink != m_Args.Bad(); ++pLink)
		{
			if(pLink->IsReturn()) continue;
			if(pLink->IsCompatible(pType)) ++n;
		}
		return n;
	}
	int GetNumCompatibleInArgsStrict(const CTypeBase * pType)
	{
		int n = 0;
		for(auto pLink = m_Args.Begin(); pLink != m_Args.Bad(); ++pLink)
		{
			if(pLink->IsReturn()) continue;
			if(pLink->IsStrictlyCompatible(pType)) ++n;
		}
		return n;
	}
	int GetNumArgsOfType(const CTypeBase * pType)
	{
		int n = 0;
		for(auto pLink = m_Args.Begin(); pLink != m_Args.Bad(); ++pLink)
		{
			if(pLink->IsReturn()) continue;
			if(!pLink->GetType()->IsExtended() && pType->IsExtended()) continue;
			if(pLink->IsStrictlyCompatible(pType)) ++n;
		}
		return n;
	}

	const CCommandArg	*	GetFirstArgOfType(const CTypeBase * pType)
	{
		for(auto pLink = m_Args.Begin(); pLink != m_Args.Bad(); ++pLink)
		{
			if(pLink->IsCompatible(pType)) return *pLink;
		}
		return nullptr;
	}

	bool DoesOverloadConflict(const CCommand * pCommand, bool bOnce = false) const
	{
		if(!bOnce)
		{
			// Check all overloads
			auto pCmd = this;

			// Rewind
			while(pCmd->m_pPrevOverload) pCmd = m_pPrevOverload;

			// Walk and recurse
			do
			{
				if(pCmd->DoesOverloadConflict(pCommand, true))
					return true;
			}
			while(pCmd = pCmd->m_pNextOverload);

			return false;
		}

		// If the number of args are inequal, its an automatic out
		if(GetNumArgs() != pCommand->GetNumArgs()) return false;

		// If one takes no args, they both take no args, and thus conflict
		if(!GetNumArgs()) return true;

		// If all arguments are strictly compatible, the overloads are strictly conflicting
		for(int i=0; i<GetNumArgs(); i++)
		{
			if(!GetArg(i)->IsStrictlyCompatible(pCommand->GetArg(i)->GetType()))
				return false;
		}
		return true;
	}
};

class CCommands
{
	static int					s_nNumCommands;
	static int					s_nNumCustomCommands;
	static CList<CCommand>		s_pCommandList;
	static CCommand	*			s_pCommands[MAX_COMMANDS];
	static CCommand *			s_pLastFoundCommand;
	//static CCommand *			s_pCustomCommands[MAX_COMMANDS];

	static CCommand * Add(DWORD id, const char *szName)
	{
		auto pCommand = new CCommand(id, szName);
		COpcodes::Register(pCommand);
		pCommand->SetIsScript(false);
		return s_pCommands[pCommand->m_nIndex = s_nNumCommands++] = pCommand;
	}

public:
	static CCommand * Add(DWORD id, const char *szName, bool bIsScript)
	{
		auto pCommand = new CCommand(id, szName);
		COpcodes::Register(pCommand);
		pCommand->SetIsScript(true);
		++s_nNumCustomCommands;
		return s_pCommands[pCommand->m_nIndex = s_nNumCommands++] = pCommand;
	}

	static void AddToList(CCommand *pCommand)
	{
		// Add the command to a searchable binary tree of hashes (for speedy lookups)
		s_pCommandList.Add(pCommand->GetHash(), pCommand);
	}

	static int Count()							{return s_nNumCommands;}

	static bool CheckCommandName(const char *szName)
	{
		// Acceptable command name chars: A-Z_
		while(char c = *szName++)
		{
			if(!IsAlphanumeric(c) && c != '_') return false;
		}
		return true;
	}

	static CCommand * Get(short id)
	{
		// Got the command ID? Get the opcode and return it as a command.
		auto pOpcode = COpcodes::Get(id);
		return pOpcode && pOpcode->GetType() == OPCODE_TYPE_COMMAND ? pOpcode->Get<CCommand>() : nullptr;
	}
	static CCommand * GetByIndex(int idx)		{return s_pCommands[idx];}
	static CCommand * GetForParsing(int idx)
	{
		// Used for retrieving compiler-generated commands such as GOTO's in high level statements
		auto pOpcode = COpcodes::GetForParsing(idx);
		return pOpcode ? pOpcode->AsCommand() : nullptr;
	}
	static CCommand * GetByName(const char *szName)
	{
		// Find the command by hashing the name and searching through a binary tree
		int hash = GenerateHash(szName);
		auto pCommand = s_pCommandList.Get(hash);
		while(pCommand && !pCommand->IsNamed(szName, hash))
			pCommand = s_pCommandList.Next();
		return pCommand;
	}

	static CCommand * FindFirstNamed(const char *szName)
	{
		return s_pLastFoundCommand = GetByName(szName);
	}
	static CCommand * FindNextNamed(const char *szName)
	{
		if(s_pLastFoundCommand)
		{
			CCommand * pCommand;
			int hash = GenerateHash(szName);
			do pCommand = s_pCommandList.Next();
			while(pCommand && !pCommand->IsNamed(szName, hash));
			return pCommand;
		}
		return nullptr;
	}

	static void Reset()
	{
		for(int i=s_nNumCommands - s_nNumCustomCommands; i < s_nNumCommands; ++i)
		{
			delete s_pCommands[i];
			s_pCommands[i] = nullptr;
		}
		s_nNumCustomCommands = 0;
	}

	static bool			LoadFromFile(const char *szFilePath);
	static char		*	ParseCommandArgList(CCommand *pCommand, char *szStr);
	static CCommand	*	ParseCommandDeclaration(DWORD opcode, char *szName);
};